package com.stars.easyms.gateway.error;

import com.stars.easyms.alarm.EasyMsAlarmService;
import com.stars.easyms.alarm.bean.EasyMsExceptionAlarmTraceInfo;
import com.stars.easyms.alarm.message.EasyMsExceptionAlarmMessage;
import com.stars.easyms.base.constant.EasyMsCommonConstants;
import com.stars.easyms.base.constant.HttpHeaderConstants;
import com.stars.easyms.base.constant.RestCodeConstants;
import com.stars.easyms.base.encrypt.EasyMsEncrypt;
import com.stars.easyms.base.bean.EasyMsResponseEntity;
import com.stars.easyms.base.enums.EncryptTypeEnum;
import com.stars.easyms.base.trace.EasyMsTraceSynchronizationManager;
import com.stars.easyms.base.util.DateTimeUtil;
import com.stars.easyms.base.util.ExceptionUtil;
import com.stars.easyms.base.util.SpringBootUtil;
import com.stars.easyms.gateway.properties.EasyMsGatewayProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.server.*;
import org.springframework.web.server.ResponseStatusException;

import java.util.Map;

/**
 * <p>className: EasyMsGatewayErrorWebExceptionHandler</p>
 * <p>description: EasyMs网关自定义异常处理类</p>
 *
 * @author guoguifang
 * @version 1.6.1
 * @date 2020/8/30 9:06 下午
 */
@Slf4j
public class EasyMsGatewayErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {

    @Autowired
    private EasyMsAlarmService easyMsAlarmService;

    @Autowired
    private EasyMsGatewayProperties gatewayProperties;

    /**
     * Create a new {@code DefaultErrorWebExceptionHandler} instance.
     *
     * @param errorAttributes    the error attributeEa
     *                           s
     * @param resourceProperties the resources configuration properties
     * @param errorProperties    the error configuration properties
     * @param applicationContext the current application context
     */
    public EasyMsGatewayErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

    @Override
    @SuppressWarnings("unchecked")
    protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        // 获取实际异常
        Throwable error = super.getError(request);

        // 记录异常信息
        log.error("路由异常:{}", error.getMessage(), error);

        // 获取请求参数
        String currentRequestPath = request.path();
        String requestSys = getRequestAttribute(request, HttpHeaderConstants.HEADER_KEY_REQUEST_SYS);
        String requestSource = getRequestAttribute(request, HttpHeaderConstants.HEADER_KEY_REQUEST_SOURCE);
        String upstreamRequestId = getRequestAttribute(request, HttpHeaderConstants.HEADER_KEY_UPSTREAM_REQUEST_ID);
        String downstreamRequestId = getRequestAttribute(request, HttpHeaderConstants.HEADER_KEY_REQUEST_ID);
        String traceId = getRequestAttribute(request, HttpHeaderConstants.TRACE_KEY);

        // 如果是404的错误不告警
        boolean isNotFound = false;
        if (error instanceof ResponseStatusException) {
            ResponseStatusException responseStatusException = (ResponseStatusException) error;
            isNotFound = responseStatusException.getStatus() == HttpStatus.NOT_FOUND;
        }

        // 异常告警
        if (!isNotFound && easyMsAlarmService.canAlarm()) {
            EasyMsExceptionAlarmTraceInfo traceInfo = new EasyMsExceptionAlarmTraceInfo();
            traceInfo.setApplicationName(SpringBootUtil.getApplicationName());
            traceInfo.setEnv(SpringBootUtil.getActiveProfile());
            traceInfo.setRequestSys(requestSys);
            traceInfo.setRequestPath(currentRequestPath);
            traceInfo.setAlarmTime(System.currentTimeMillis());
            traceInfo.setExceptionClassName(error.getClass().getName());
            traceInfo.setExceptionMessage(error.getMessage());
            traceInfo.setFirstExceptionStack(ExceptionUtil.getFirstExceptionStack(error));
            traceInfo.setRequestId(upstreamRequestId);
            traceInfo.setTraceId(traceId);
            EasyMsExceptionAlarmMessage exceptionAlarmMessage = new EasyMsExceptionAlarmMessage();
            exceptionAlarmMessage.setExceptionTraceInfo(traceInfo);
            easyMsAlarmService.sendExceptionAlarmMessage(exceptionAlarmMessage);
        }

        // 封装返回
        EasyMsResponseEntity<?> responseEntity = new EasyMsResponseEntity<>();
        responseEntity.setRetCode(RestCodeConstants.DEFAULT_ERROR_CODE);
        responseEntity.setRetMsg(RestCodeConstants.DEFAULT_ERROR_MSG);
        responseEntity.setErrorDesc(ExceptionUtil.getExceptionDesc(error));
        responseEntity.setTraceId(traceId);

        // 记录请求日志
        log.info("[路由-返回响应]-[请求地址: {}]-[请求系统: {}]{}-[请求ID: {}]-[响应时间: {}]-响应数据: {}",
                currentRequestPath, requestSys,
                StringUtils.isNotBlank(requestSource) ? "-[系统来源: " + requestSource + "]" : "",
                downstreamRequestId, DateTimeUtil.now(), responseEntity);

        EasyMsTraceSynchronizationManager.clearTraceInfo();

        // 如果需要加密则进行加密
        EasyMsGatewayProperties.Source source = gatewayProperties.getSource(requestSource);
        EasyMsGatewayProperties.Encrypt encrypt = source.getEncrypt();
        if (encrypt.isEnabled()) {
            EncryptTypeEnum encryptType = encrypt.getType();
            try {
                if (EncryptTypeEnum.SYMMETRIC == encryptType) {
                    EasyMsGatewayProperties.Symmetric symmetric = encrypt.getSymmetric();
                    return (Map<String, Object>) EasyMsEncrypt.encrypt(responseEntity, symmetric.getSecret(),
                            symmetric.getIv(), symmetric.getKey());
                }
            } catch (Exception e) {
                log.error("响应返回值加密失败, 加密方式: {}!", encryptType.name(), e);
            }
        }
        return responseEntity.toMap();
    }

    @Override
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        return HttpStatus.OK.value();
    }

    private String getRequestAttribute(ServerRequest request, String key) {
        return request.attribute(key).orElse(EasyMsCommonConstants.UNKNOWN).toString();
    }

}
